Many computer programs have been written in the Java programming language. If a program is written in Java, it can be run on any machine on which a Java virtual machine (JVM) is executing. This is so regardless of the hardware used in the machine, and regardless of the operating system executing on the machine. Thus, a Java program is hardware and operating system independent.
Java is an object oriented programming language. Thus, a Java program comprises a plurality of object classes, and each object class can have zero or more methods. When a Java program is executed, the methods of the object classes are invoked and executed.
A java method may be executed in one of two ways. One way is for the JVM to execute the method interpretively. More specifically, before a Java program is executed, the source code of the program (and hence, the source code of the methods) is broken down into Java bytecodes. At runtime, the Java interpreter of the JVM takes the bytecodes of a method and interprets them. In effect, the interpreter executes the bytecodes to give the method effect. Executing the method in this way is effective, but because the execution is carried out in an interpretive manner, it is relatively slow.
As an alternative, the JVM may choose not to interpret a method, but rather compile the bytecodes of the method down into native code. The JVM then causes the native code to be executed directly by the processor(s) of the machine. By doing this, the JVM causes subsequent execution of the method to be performed much faster (since it is now executed natively rather than interpretively). Often, the JVM decides to compile a method after the method has been invoked several times. To perform the compilation, the JVM invokes a dynamic compiler known as a JIT (just-in-time) compiler, which is part of the JVM. The JIT compiler is dynamic in that, unlike other compilers, it performs the method compilation at runtime.
Like any other compiler, it is a goal of the JIT compiler to produce native code that executes in an optimized manner. One way to optimize the execution of native code is to structure the code in such a way that often-used local variables are maintained in hardware registers as much as possible. Doing so reduces the need to load the local variables from memory, which is a time consuming process. The approach traditional compilers have taken to keep local variables in registers is to perform a global analysis of a method and to compute scores for each local variable for certain sections of the method. The scores reflect how important it is to keep a local variable in a register for a certain section of the method. Based on the scores, the traditional compilers allocate registers to certain local variables. Once allocated, the registers are dedicated to the local variables over a specific section of the method (i.e. over a specific section of the method's code).
While this approach is effective for keeping local variables in dedicated registers, it has a significant drawback in that it is relatively heavyweight. Since the JIT compiler compiles methods during runtime, using such a heavyweight process to compile methods would significantly slow down program execution. This slowdown, in turn, might outweigh any benefits gained from the resultant optimized code. Also, the code required to implement the traditional approach is relatively heavyweight, and the memory that it consumes during execution is large. In many implementations, the JIT compiler is used in an embedded device with limited resources. In such implementations, the traditional approach just cannot be used due to resource constraints. Thus, for at least the above reasons, the JIT compiler cannot practicably use the traditional approach for keeping local variables in registers.